Skip to content

feat(store): expose mmap_size via CBM_SQLITE_MMAP_SIZE env#315

Merged
DeusData merged 1 commit intoDeusData:mainfrom
edwardmhughes:fix/mmap-env-knob
May 10, 2026
Merged

feat(store): expose mmap_size via CBM_SQLITE_MMAP_SIZE env#315
DeusData merged 1 commit intoDeusData:mainfrom
edwardmhughes:fix/mmap-env-knob

Conversation

@edwardmhughes
Copy link
Copy Markdown
Contributor

Summary

Hard-coded PRAGMA mmap_size = 67108864 left no path for users running multiple codebase-memory-mcp instances against the same cache to opt out of memory-mapped I/O.

On macOS, when one instance's checkpoint or reindex truncates the DB file under another instance's live mmap, accessing the now-missing pages raises SIGBUS, which takes down the process. Setting CBM_SQLITE_MMAP_SIZE=0 reverts to read()/pread() I/O, which returns recoverable SQLITE_IOERR instead.

Changes

  • src/store/store.c: Extracts cbm_store_resolve_mmap_size() — reads CBM_SQLITE_MMAP_SIZE, validates with strtoll, clamps negative to 0, falls back to default 67108864 on missing/malformed input
  • src/store/store.h: Declares cbm_store_resolve_mmap_size() for tests
  • tests/test_store_pragmas.c (new): 7 tests covering default, zero, explicit value, negative clamp, garbage input, partial garbage, and store-open smoke test
  • Makefile.cbm + tests/test_main.c: Wire new test suite

Behavior

Scenario Before After
CBM_SQLITE_MMAP_SIZE unset PRAGMA mmap_size = 67108864 unchanged
CBM_SQLITE_MMAP_SIZE=0 n/a PRAGMA mmap_size = 0 (disables mmap)
CBM_SQLITE_MMAP_SIZE=1048576 n/a PRAGMA mmap_size = 1048576
Invalid value n/a logs warning, falls back to 67108864

Single-instance users: no behavior change.

Fixes #314

Hard-coded `PRAGMA mmap_size = 67108864` in configure_pragmas() left
no path for users running multiple cbm-mcp instances against the
same cache to opt out of memory-mapped I/O. On macOS, when one
instance's checkpoint or reindex truncates the DB file under
another instance's live mmap, accessing the now-missing pages
raises SIGBUS, taking the process down.

Setting CBM_SQLITE_MMAP_SIZE=0 reverts to read()/pread() I/O,
which returns recoverable SQLITE_IOERR instead of crashing the
process.

- Default unchanged (67108864 / 64 MB). No behavior change for
  single-instance users.
- Malformed values (non-numeric, partial-numeric) fall back to
  the default rather than failing the store open.
- Negative values clamp to 0.
- New tests: tests/test_store_pragmas.c covers all five resolver
  paths plus an integration smoke that opens a file-backed store
  with mmap disabled.

Empirical evidence: 9 SIGBUS crash reports collected on macOS
arm64 v0.6.0 in a 14-hour window, all signature 'cluster_pagein
past EOF' with stacks bottoming in SQLite btree code under the
watcher thread's incremental-index pipeline.
@DeusData DeusData added enhancement New feature or request stability/performance Server crashes, OOM, hangs, high CPU/memory labels May 4, 2026
@DeusData DeusData merged commit 093707c into DeusData:main May 10, 2026
@DeusData
Copy link
Copy Markdown
Owner

Merged via rebase, thanks @edwardmhughes. Diagnosis is precise — the macOS SIGBUS-on-truncated-mmap interaction is exactly the kind of failure mode that needs a knob rather than a code fix, since the safe-by-default value (0) trades off ~real performance for crash safety, and most users on a single instance shouldn't pay that cost.

The implementation is solid:

  • cbm_safe_getenv (project's wrapper rather than raw getenv) is the right call.
  • The strtoll validation pattern (end == buf for no-digits AND *end != '\0' for trailing garbage) catches both empty and partial-numeric inputs — the test cases for not-a-number and 123abc confirm both branches.
  • Negative→0 clamp is the right shape (SQLite interprets mmap_size = 0 as disable, so the clamp aligns the env semantics with SQLite's).
  • Default unchanged, single-instance users see no behavior change.

Test coverage is comprehensive — 7 cases covering default/zero/explicit/negative/garbage/partial-garbage/integration-smoke. The integration smoke test (opening a file-backed store with mmap_size=0) is the right shape for proving the resolver is actually wired into configure_pragmas rather than just being a standalone function.

Issue #314 auto-closed with the merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request stability/performance Server crashes, OOM, hangs, high CPU/memory

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SIGBUS on macOS arm64 when 2+ sessions share the same cache dir

2 participants